﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using Microsoft.TeamFoundation;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.WorkItemTracking.Client;


namespace TfsLinker
{
    /// <summary>
    /// Links work items on a TFS.
    /// </summary>
    class TfsLinker
    {
        /// <summary>
        /// Work item store.
        /// </summary>
        WorkItemStore WiStore;

        /// <summary>
        /// TFS connection.
        /// </summary>
        TeamFoundationServer Tfs = null;

        /// <summary>
        /// Logging instance.
        /// </summary>
        ILogging Logging;


        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="logging">Instance of a ILogging implementing class.</param>
        public TfsLinker(ILogging logging)
        {
            this.Logging = logging;
        }

        /// <summary>
        /// Connects to TFS.
        /// </summary>
        /// <param name="serverAddress">Address of TFS.</param>
        public void Connect(string serverAddress)
        {
            WriteToLog("Trying to connect ...");


            //Init credentials and TFS provider
            ICredentialsProvider provider = new UICredentialsProvider();

            //Connect to TFS
            try
            {
                Tfs = TeamFoundationServerFactory.GetServer(serverAddress, provider);
            }
            catch (InvalidProgramException)
            {
                //Occurs, when apis are not correctly installed on the machine.
                WriteToLog("API error.");
                throw new CriticalException("Cannot use Team Foundation Server API correctly. Verify that you run this on the server having installed TFS.");
            }
            catch (TeamFoundationInvalidServerNameException)
            {
                //Invalid uri. Signal this to user.
                WriteToLog("Invalid URI. Please enter the correct one (http://server:port).");
                throw new NonCriticalException();
            }
        }

        /// <summary>
        /// Authenticates with the server.
        /// Precondition: Already connected.
        /// </summary>
        public void Authenticate()
        {
            if (Tfs == null)
                throw new CriticalException("Internal exception. Tfs is not set.");

            WriteToLog("Authenticating ...");

            //Authenticate with TFS
            try
            {
                Tfs.Authenticate();
            }
            catch (TeamFoundationServerUnauthorizedException)
            {
                //Authentication failed. Unautorized user.
                WriteToLog("Could not authenticate. Please verify that you are logged in with appropriate credentials.");
                throw new NonCriticalException();
            }
            catch (WebException)
            {
                //Connection to server failed.
                WriteToLog("Could not connect to server. Please enter correct URI.");
                throw new NonCriticalException();
            }

            //Get work item store
            try
            {
                WiStore = Tfs.GetService(typeof(WorkItemStore)) as WorkItemStore;
            }
            catch
            {
                WriteToLog("Could not get work item store.");
                throw new NonCriticalException();
            }
        }

        /// <summary>
        /// Get all projects on TFS.
        /// Precondition: Already connected and authenticated.
        /// </summary>
        /// <returns>A array of ProjectStructs.</returns>
        public ProjectStruct[] GetProjects()
        {
            if (WiStore != null)
            {
                ProjectStruct[] ps = new ProjectStruct[WiStore.Projects.Count];
                for (int a = 0; a < ps.Length; a++)
                    ps[a] = new ProjectStruct(WiStore.Projects[a]);
                return ps;
            }
            else
                return null;
        }

        /// <summary>
        /// Links the passed project.
        /// Precondition: Already connected and authenticated.
        /// </summary>
        /// <param name="project">Project to be linked.</param>
        public void Link(ProjectStruct project)
        {
            //Get project
            Project projectToLink = project.Proj;

            //Query needed work items
            WriteToLog("Querying needed work items ...");

            WorkItemCollection decisionGateCollection, productCollection, activityCollection, taskCollection;
            try
            {
                decisionGateCollection = WiStore.Query(string.Format(Resources.Query_DecisionGates, projectToLink.Name));
                productCollection = WiStore.Query(string.Format(Resources.Query_Products, projectToLink.Name));
                activityCollection = WiStore.Query(string.Format(Resources.Query_Activities, projectToLink.Name));
                taskCollection = WiStore.Query(string.Format(Resources.Query_Tasks, projectToLink.Name));
            }
            catch (ValidationException)
            {
                WriteToLog("Queries failed.");
                throw new NonCriticalException();
            }


            WriteToLog("Linking ...");

            //Link products and decision gates according to activities
            foreach (WorkItem curActivity in activityCollection)
            {
                WriteToLog(" ... activity: " + curActivity.Fields["System.Title"].Value as string);

                //Get decision gate and product linked through this activity
                WorkItem decisionGate = GetWorkItemById(decisionGateCollection, curActivity.Fields["VMXT.EPRef"].Value as string);
                WorkItem product = GetWorkItemById(productCollection, curActivity.Fields["VMXT.PRef"].Value as string);


                if (decisionGate != null)
                {
                    //Link decision gate - activity
                    try
                    {
                        curActivity.Links.Add(new RelatedLink(decisionGate.Id));
                    }
                    catch (ValidationException) { } //Link does already exist.
                }


                if (product != null)
                {
                    //Link product - activity
                    try
                    {
                        curActivity.Links.Add(new RelatedLink(product.Id));
                    }
                    catch (ValidationException) { } //Link does already exist.


                    //Link product - decision gate
                    if (decisionGate != null)
                    {
                        try
                        {
                            decisionGate.Links.Add(new RelatedLink(product.Id));
                        }
                        catch (ValidationException) { } //Link does already exist

                        decisionGate.Save(); //Save decision gate
                    }
                }

                //Save changed activity
                curActivity.Save();
            }

            //Link tasks to belonging activities
            foreach (WorkItem curTask in taskCollection)
            {
                WriteToLog(" ... task: " + curTask.Fields["System.Title"].Value as string);

                //Get belonging activity
                WorkItem activity = GetWorkItemById(activityCollection, curTask.Fields["VMXT.ARef"].Value as string);
                if (activity != null)
                {
                    //Link activity - task
                    try
                    {
                        curTask.Links.Add(new RelatedLink(activity.Id));
                    }
                    catch (ValidationException) { } //Link does already exist

                    //Save changed task
                    curTask.Save();
                }
            }
        }


        /// <summary>
        /// Gets the work item having the specified ID from the passed collection.
        /// </summary>
        /// <param name="collection">Work item collection to be searched through.</param>
        /// <param name="id">ID of the work item to be found.</param>
        /// <returns>The searched work item or null if it has not been found.</returns>
        private WorkItem GetWorkItemById(WorkItemCollection collection, string id)
        {
            foreach (WorkItem curWI in collection)
            {
                if ((curWI.Fields["VMXT.id"].Value as string).Equals(id, StringComparison.OrdinalIgnoreCase))
                    return curWI;
            }
            return null;
        }

        /// <summary>
        /// Writes something to an ILogging.
        /// </summary>
        /// <param name="str">String to be written.</param>
        private void WriteToLog(string str)
        {
            if (Logging != null)
                Logging.WriteLogEntry(str);
        }
    }

    /// <summary>
    /// Stores a TFS project.
    /// </summary>
    struct ProjectStruct
    {
        /// <summary>
        /// The project.
        /// </summary>
        public Project Proj;


        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="project">The project.</param>
        public ProjectStruct(Project project)
        {
            if (project == null)
                throw new ArgumentNullException("Project must not be null.");
            Proj = project;
        }

        /// <summary>
        /// Formats the project a string.
        /// </summary>
        /// <returns>Project name and ID as string.</returns>
        public override string ToString()
        {
            return Proj.Name + ", ID: " + Proj.Id.ToString();
        }
    }
}
